package com.yvan.codeslave.batch;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Joiner;
import com.yvan.codeslave.BeetlUtils;
import com.yvan.codeslave.batch.process.ActionProcess;
import com.yvan.codeslave.batch.process.FileProcess;
import com.yvan.codeslave.batch.process.JavaProcess;
import com.yvan.codeslave.batch.process.TableProcess;
import com.yvan.platform.*;
import javafx.util.Pair;
import lombok.SneakyThrows;
import lombok.val;
import org.apache.commons.io.IOUtils;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
import java.util.Map;

public class CodeSlave {

    private static final String XSI_TABLE = "table";
    private static final String XSI_FILE_GENERATOR = "fileGenerator";
    private static final String XSI_JAVA_GENERATOR = "javaGenerator";

    public static volatile CodeSlave INSTANCE = new CodeSlave();
    public CodeSlaveSetting codeSlaveSetting;
    private Map<String, TemplateSetting> templateCache;
    private Map<String, Object> globalProperty;
    private final GroupTemplate stringGroupTemplate;

    @SneakyThrows
    private CodeSlave() {
        stringGroupTemplate = BeetlUtils.createStringGroupTemplate();
    }

    public void reload() {
        if (codeSlaveSetting == null) {
            throw new RuntimeException("codeSlaveSetting 不能为空");
        }

        codeSlaveSetting.reload();
        globalProperty = codeSlaveSetting.getJw().asMap("global");

        templateCache = codeSlaveSetting.getTemplates();
        for (TemplateSetting templateSetting : templateCache.values()) {
            templateSetting.init(codeSlaveSetting);
        }
    }

    /**
     * 查看 Model 最终效果
     *
     * @param filePath module文件路径
     */
    Pair<JsonWapper, TemplateSetting> processModule(String filePath) {
        //检查文件和设置
        if (codeSlaveSetting == null) {
            throw new RuntimeException("codeSlaveSetting 不能为空");
        }

        File file = new File(filePath);
        if (!file.exists() || !file.isFile()) {
            throw new RuntimeException("非法路径:" + filePath);
        }

        //找到模版
        TemplateSetting templateSetting;
        JsonNode moduleNode = XmlUtils.readXmlToNode(file);
        try {
            String templateName = moduleNode.get("template").asText();
            templateSetting = templateCache.get(templateName);
            if (templateSetting == null) {
                throw new RuntimeException("没有定义模版:" + templateName);
            }
        } catch (Exception e) {
            throw new RuntimeException("没有定义 template 属性:" + filePath, e);
        }

        //将模版中的基类文件与当前模块合并
        JsonNode node = templateSetting.getJsonNode().deepCopy();
        YvanUtil.merge(node, moduleNode);

        //替换变量，并生成 Module 文件
        JsonWapper jw = randerTemplate(node);

        //找到所有 Table，并重新定义 Columns 的属性
        //处理表格
        for (val table : util.tableNames(jw)) {
            TableProcess.process(jw.asDic(table));
        }

        //定义 actions
        //TableProcess.processActions(jw);
        ActionProcess.process(jw);

        //处理 JavaFile
        JavaProcess.process(jw);

        FileProcess.process(jw);
        return new Pair<>(jw, templateSetting);
    }

    /**
     * 执行代码生成
     *
     * @param filePath module文件路径
     */
    @SneakyThrows
    public String run(String projectPath, String filePath) {
        val kv = processModule(filePath);
        JsonWapper jw = kv.getKey();
        TemplateSetting templateSetting = kv.getValue();
        StringBuilder sb = new StringBuilder();

        val rootPath = FileUtils.normalizePath(codeSlaveSetting.getTemplateBase());
        val templateEngin = BeetlUtils.createFileSystemGroupTemplate(rootPath);

        for (val fileKey : jw.keys(Consts.File)) {
            val jwFile = jw.asDic(Consts.File, fileKey);
            if (jwFile.asBoolean(Consts.JAVA_DISABLE)) {
                continue;
            }
            val r = templateFileProcess(projectPath, templateEngin, templateSetting, fileKey, jw, jwFile, jw.asDic(jwFile.asStr("forTable")));
            sb.append(r).append("\r\n");
        }
        return sb.toString();
    }

    @SneakyThrows
    private String templateFileProcess(String projectPath,
                                       GroupTemplate templateEngin,
                                       TemplateSetting templateSetting,
                                       String targetFilePath,
                                       JsonWapper module,
                                       JsonWapper jwFile,
                                       JsonWapper jwTable) {

        targetFilePath = targetFilePath.trim();
        if (FileUtils.isWindows() && targetFilePath.startsWith("./")) {
            targetFilePath = FileUtils.normalizePath(projectPath, targetFilePath.substring(2));
        }
        if (!FileUtils.isWindows() && targetFilePath.startsWith("/./")) {
            targetFilePath = FileUtils.normalizePath(projectPath, targetFilePath.substring(3));
        }

        File targetFile = FileUtils.getResource(targetFilePath);

        Template t = templateEngin.getTemplate(
                FileUtils.normalizePath(templateSetting.getTemplateBasePath(), jwFile.asStr("template"))
        );

        try {
            File parent = targetFile.getParentFile();
            if (!parent.exists()) {
                parent.mkdirs();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


        t.binding("_root", globalProperty);
        t.binding(module.getInnerMap());
        t.binding("myTable", jwTable.getInnerMap());
        t.binding("myFile", jwFile.getInnerMap());
        t.binding("this", module.getInnerMap());
        try (FileOutputStream outputStream = new FileOutputStream(targetFile)) {
            t.renderTo(outputStream);
        }
        return targetFile.getCanonicalPath();
    }

    @SneakyThrows
    private String templateFileProcess(File templateFile) {
        try (FileInputStream fis = new FileInputStream(templateFile)) {
            List<String> lines = IOUtils.readLines(fis, "UTF-8");
            if (lines.size() > 0 && lines.get(0).contains("@formatter:")) {
                return Joiner.on("\r\n").join(lines.subList(1, lines.size()));
            }
            return Joiner.on("\r\n").join(lines);
        }
    }

    @SneakyThrows
    private JsonWapper randerTemplate(JsonNode node) {
        JsonWapper jw = new JsonWapper(node);
        Template template = stringGroupTemplate.getTemplate(jw.toString(true));
        template.binding("_root", globalProperty);
        template.binding(jw.getInnerMap());
        String renderResult = template.render();
        while (renderResult.contains("${") || renderResult.contains("<%")) {
            template = stringGroupTemplate.getTemplate(renderResult);
            template.binding("_root", globalProperty);
            template.binding(new JsonWapper(renderResult).getInnerMap());
            renderResult = template.render();
        }
        return new JsonWapper(renderResult);
    }
}
